using System;
using System.Collections.Generic;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.core
{


	using TransactionManager = javax.transaction.TransactionManager;

	using Direction = org.neo4j.graphdb.Direction;
	using Node = org.neo4j.graphdb.Node;
	using Relationship = org.neo4j.graphdb.Relationship;
	using RelationshipType = org.neo4j.graphdb.RelationshipType;
	using Transaction = org.neo4j.graphdb.Transaction;
	using EmbeddedGraphDatabase = org.neo4j.kernel.EmbeddedGraphDatabase;
	using AbstractNeo4jTestCase = org.neo4j.kernel.impl.AbstractNeo4jTestCase;
	using MyRelTypes = org.neo4j.kernel.impl.MyRelTypes;
	using NodeManager = org.neo4j.kernel.impl.core.NodeManager;

	public class TestNeo4jCacheAndPersistence : AbstractNeo4jTestCase
	{
		public TestNeo4jCacheAndPersistence(string testName) : base(testName)
		{
		}

		private int node1Id = -1;
		private int node2Id = -1;
		private string key1 = "key1";
		private string key2 = "key2";
		private string arrayKey = "arrayKey";
		private int int1 = new int(1);
		private int int2 = new int(2);
		private string string1 = new string("1");
		private string string2 = new string("2");
		private int[] array = new int[] { 1, 2, 3, 4, 5, 6, 7 };

		public override void setUp()
		{
			base.setUp();
			Node node1 = getGraphDb().createNode();
			Node node2 = getGraphDb().createNode();
			Relationship rel = node1.createRelationshipTo(node2, MyRelTypes.TEST);
			node1Id = (int) node1.getId();
			node2Id = (int) node2.getId();
			node1.setProperty(key1, int1);
			node1.setProperty(key2, string1);
			node2.setProperty(key1, int2);
			node2.setProperty(key2, string2);
			rel.setProperty(key1, int1);
			rel.setProperty(key2, string1);
			node1.setProperty(arrayKey, array);
			node2.setProperty(arrayKey, array);
			rel.setProperty(arrayKey, array);
			Transaction tx = getTransaction();
			tx.success();
			tx.finish();
			NodeManager nodeManager = ((EmbeddedGraphDatabase) getGraphDb()).getConfig().getNeoModule().getNodeManager();
			nodeManager.clearCache();
			tx = getGraphDb().beginTx();
			setTransaction(tx);
		}

		public override void tearDown()
		{
			Node node1 = getGraphDb().getNodeById(node1Id);
			Node node2 = getGraphDb().getNodeById(node2Id);
			node1.getSingleRelationship(MyRelTypes.TEST, Direction.BOTH).delete();
			node1.delete();
			node2.delete();
			base.tearDown();
		}

		public virtual void testAddProperty()
		{
			string key3 = "key3";

			Node node1 = getGraphDb().getNodeById(node1Id);
			Node node2 = getGraphDb().getNodeById(node2Id);
			Relationship rel = node1.getSingleRelationship(MyRelTypes.TEST, Direction.BOTH);
		// add new property
			node2.setProperty(key3, int1);
			rel.setProperty(key3, int2);
			assertTrue(node1.hasProperty(key1));
			assertTrue(node2.hasProperty(key1));
			assertTrue(node1.hasProperty(key2));
			assertTrue(node2.hasProperty(key2));
			assertTrue(node1.hasProperty(arrayKey));
			assertTrue(node2.hasProperty(arrayKey));
			assertTrue(rel.hasProperty(arrayKey));
			assertTrue(!node1.hasProperty(key3));
			assertTrue(node2.hasProperty(key3));
			assertEquals(int1, node1.getProperty(key1));
			assertEquals(int2, node2.getProperty(key1));
			assertEquals(string1, node1.getProperty(key2));
			assertEquals(string2, node2.getProperty(key2));
			assertEquals(int1, rel.getProperty(key1));
			assertEquals(string1, rel.getProperty(key2));
			assertEquals(int2, rel.getProperty(key3));
		}

		public virtual void testNodeRemoveProperty()
		{
			Node node1 = getGraphDb().getNodeById(node1Id);
			Node node2 = getGraphDb().getNodeById(node2Id);
			Relationship rel = node1.getSingleRelationship(MyRelTypes.TEST, Direction.BOTH);

		// test remove property
			assertEquals(1, node1.removeProperty(key1));
			assertEquals(2, node2.removeProperty(key1));
			assertEquals(1, rel.removeProperty(key1));
			assertEquals(string1, node1.removeProperty(key2));
			assertEquals(string2, node2.removeProperty(key2));
			assertEquals(string1, rel.removeProperty(key2));
			assertTrue(node1.removeProperty(arrayKey) != null);
			assertTrue(node2.removeProperty(arrayKey) != null);
			assertTrue(rel.removeProperty(arrayKey) != null);
		}

		public virtual void testNodeChangeProperty()
		{
			Node node1 = getGraphDb().getNodeById(node1Id);
			Node node2 = getGraphDb().getNodeById(node2Id);
			Relationship rel = node1.getSingleRelationship(MyRelTypes.TEST, Direction.BOTH);

		// test change property
			node1.setProperty(key1, int2);
			node2.setProperty(key1, int1);
			rel.setProperty(key1, int2);
			int[] newIntArray = new int[] { 3, 2, 1 };
			node1.setProperty(arrayKey, newIntArray);
			node2.setProperty(arrayKey, newIntArray);
			rel.setProperty(arrayKey, newIntArray);
		}

		public virtual void testNodeGetProperties()
		{
			Node node1 = getGraphDb().getNodeById(node1Id);

			assertTrue(!node1.hasProperty(null));
			Iterator<object> values = node1.getPropertyValues().GetEnumerator();
			values.Current;
			values.Current;
			Iterator<string> keys = node1.getPropertyKeys().GetEnumerator();
			keys.Current;
			keys.Current;
			assertTrue(node1.hasProperty(key1));
			assertTrue(node1.hasProperty(key2));
		}

		private Relationship[] getRelationshipArray(Iterable<Relationship> relsIterable)
		{
			List<Relationship> relList = new List<Relationship>();
			foreach (Relationship rel in relsIterable)
			{
				relList.Add(rel);
			}
			return relList.ToArray(new Relationship[relList.Count]);
		}

		public virtual void testDirectedRelationship1()
		{
			Node node1 = getGraphDb().getNodeById(node1Id);
			Relationship rel = node1.getSingleRelationship(MyRelTypes.TEST, Direction.BOTH);
			Node[] nodes = rel.getNodes();
			assertEquals(2, nodes.Length);

			Node node2 = getGraphDb().getNodeById(node2Id);
			assertTrue(nodes[0].Equals(node1) && nodes[1].Equals(node2));
			assertEquals(node1, rel.getStartNode());
			assertEquals(node2, rel.getEndNode());

			Relationship[] relArray = getRelationshipArray(node1.getRelationships(MyRelTypes.TEST, Direction.OUTGOING));
			assertEquals(1, relArray.Length);
			assertEquals(rel, relArray[0]);
			relArray = getRelationshipArray(node2.getRelationships(MyRelTypes.TEST, Direction.INCOMING));
			assertEquals(1, relArray.Length);
			assertEquals(rel, relArray[0]);
		}

		public virtual void testRelCountInSameTx()
		{
			Node node1 = getGraphDb().createNode();
			Node node2 = getGraphDb().createNode();
			Relationship rel = node1.createRelationshipTo(node2, MyRelTypes.TEST);
			assertEquals(1, getRelationshipArray(node1.getRelationships()).length);
			assertEquals(1, getRelationshipArray(node2.getRelationships()).length);
			rel.delete();
			assertEquals(0, getRelationshipArray(node1.getRelationships()).length);
			assertEquals(0, getRelationshipArray(node2.getRelationships()).length);
			node1.delete();
			node2.delete();
		}

		public virtual void testGetDirectedRelationship()
		{
			Node node1 = getGraphDb().getNodeById(node1Id);
			Relationship rel = node1.getSingleRelationship(MyRelTypes.TEST, Direction.OUTGOING);
			assertEquals(int1, rel.getProperty(key1));
		}

		public virtual void testSameTxWithArray()
		{
			getTransaction().success();
			getTransaction().finish();
			newTransaction();

			Node nodeA = getGraphDb().createNode();
			Node nodeB = getGraphDb().createNode();
			Relationship relA = nodeA.createRelationshipTo(nodeB, MyRelTypes.TEST);
			nodeA.setProperty(arrayKey, array);
			relA.setProperty(arrayKey, array);
			NodeManager nodeManager = ((EmbeddedGraphDatabase) getGraphDb()).getConfig().getNeoModule().getNodeManager();
			nodeManager.clearCache();
			assertTrue(nodeA.getProperty(arrayKey) != null);
			assertTrue(relA.getProperty(arrayKey) != null);
			relA.delete();
			nodeA.delete();
			nodeB.delete();

		}

		public virtual void testAddCacheCleared()
		{
			Node nodeA = getGraphDb().createNode();
			nodeA.setProperty("1", 1);
			Node nodeB = getGraphDb().createNode();
			Relationship rel = nodeA.createRelationshipTo(nodeB, MyRelTypes.TEST);
			rel.setProperty("1", 1);
			getTransaction().success();
			getTransaction().finish();
			newTransaction();
			NodeManager nodeManager = ((EmbeddedGraphDatabase) getGraphDb()).getConfig().getNeoModule().getNodeManager();
			nodeManager.clearCache();
			nodeA.createRelationshipTo(nodeB, MyRelTypes.TEST);
			int count = 0;
			foreach (Relationship relToB in nodeA.getRelationships(MyRelTypes.TEST))
			{
				count++;
			}
			assertEquals(2, count);
			nodeA.setProperty("2", 2);
			assertEquals(1, nodeA.getProperty("1"));
			rel.setProperty("2", 2);
			assertEquals(1, rel.getProperty("1"));
			nodeManager.clearCache();
		// trigger empty load
			getGraphDb().getNodeById(nodeA.getId());
			getGraphDb().getRelationshipById(rel.getId());
		// apply COW maps
			getTransaction().success();
			getTransaction().finish();
			newTransaction();
			count = 0;
			foreach (Relationship relToB in nodeA.getRelationships(MyRelTypes.TEST))
			{
				count++;
			}
			assertEquals(2, count);
			assertEquals(1, nodeA.getProperty("1"));
			assertEquals(1, rel.getProperty("1"));
			assertEquals(2, nodeA.getProperty("2"));
			assertEquals(2, rel.getProperty("2"));
		}

		public virtual void testTxCacheLoadIsolation()
		{
			Node node = getGraphDb().createNode();
			node.setProperty("someproptest", "testing");
			Node node1 = getGraphDb().createNode();
			node1.setProperty("someotherproptest", 2);
			commit();
			EmbeddedGraphDatabase graphDb = (EmbeddedGraphDatabase) getGraphDb();
			TransactionManager txManager = graphDb.getConfig().getTxModule().getTxManager();
			NodeManager nodeManager = graphDb.getConfig().getNeoModule().getNodeManager();
			try
			{
				txManager.begin();
				node.setProperty("someotherproptest", "testing2");
				Relationship rel = node.createRelationshipTo(node1, MyRelTypes.TEST);
				javax.transaction.Transaction txA = txManager.suspend();
				txManager.begin();
				assertEquals("testing", node.getProperty("someproptest"));
				assertTrue(!node.hasProperty("someotherproptest"));
				assertTrue(!node.hasRelationship());
				nodeManager.clearCache();
				assertEquals("testing", node.getProperty("someproptest"));
				assertTrue(!node.hasProperty("someotherproptest"));
				javax.transaction.Transaction txB = txManager.suspend();
				txManager.resume(txA);
				assertEquals("testing", node.getProperty("someproptest"));
				assertTrue(node.hasProperty("someotherproptest"));
				assertTrue(node.hasRelationship());
				nodeManager.clearCache();
				assertEquals("testing", node.getProperty("someproptest"));
				assertTrue(node.hasProperty("someotherproptest"));
				assertTrue(node.hasRelationship());
				txManager.suspend();
				txManager.resume(txB);
				assertEquals("testing", node.getProperty("someproptest"));
				assertTrue(!node.hasProperty("someotherproptest"));
				assertTrue(!node.hasRelationship());
				txManager.rollback();
				txManager.resume(txA);
				node.delete();
				node1.delete();
				rel.delete();
				txManager.commit();
				newTransaction();
			}
			catch (Exception e)
			{
				fail("" + e);
			}
		}

		public virtual void testNodeMultiRemoveProperty()
		{
			Node node = getGraphDb().createNode();
			node.setProperty("key0", "0");
			node.setProperty("key1", "1");
			node.setProperty("key2", "2");
			node.setProperty("key3", "3");
			node.setProperty("key4", "4");
			newTransaction();
			node.removeProperty("key3");
			node.removeProperty("key2");
			node.removeProperty("key3");
			newTransaction();
			getNodeManager().clearCache();
			assertEquals("0", node.getProperty("key0"));
			assertEquals("1", node.getProperty("key1"));
			assertEquals("4", node.getProperty("key4"));
			assertTrue(!node.hasProperty("key2"));
			assertTrue(!node.hasProperty("key3"));
			node.delete();
		}

		public virtual void testRelMultiRemoveProperty()
		{
			Node node1 = getGraphDb().createNode();
			Node node2 = getGraphDb().createNode();
			Relationship rel = node1.createRelationshipTo(node2, MyRelTypes.TEST);
			rel.setProperty("key0", "0");
			rel.setProperty("key1", "1");
			rel.setProperty("key2", "2");
			rel.setProperty("key3", "3");
			rel.setProperty("key4", "4");
			newTransaction();
			rel.removeProperty("key3");
			rel.removeProperty("key2");
			rel.removeProperty("key3");
			newTransaction();
			getNodeManager().clearCache();
			assertEquals("0", rel.getProperty("key0"));
			assertEquals("1", rel.getProperty("key1"));
			assertEquals("4", rel.getProperty("key4"));
			assertTrue(!rel.hasProperty("key2"));
			assertTrue(!rel.hasProperty("key3"));
			rel.delete();
			node1.delete();
			node2.delete();
		}

		public virtual void testRelationshipCahinIterator()
		{
			Node node1 = getGraphDb().createNode();
			Node node2 = getGraphDb().createNode();
			Relationship[] rels = new Relationship[100];
			for (int i = 0; i < rels.Length; i++)
			{
				if (i < 50)
				{
					rels[i] = node1.createRelationshipTo(node2, MyRelTypes.TEST);
				}
				else
				{
					rels[i] = node2.createRelationshipTo(node1, MyRelTypes.TEST);
				}
			}
			newTransaction();
			getNodeManager().clearCache();
			Iterable<Relationship> relIterable = node1.getRelationships();
			foreach (Relationship rel in rels)
			{
				rel.delete();
			}
			newTransaction();
			foreach (Relationship rel in relIterable)
			{
				Console.WriteLine(rel);
			}
			node1.delete();
			node2.delete();
		}

		public virtual void testLowGrabSize()
		{
			Map<string, string> config = new Dictionary<string, string>();
			config.put("relationship_grab_size", "1");
			EmbeddedGraphDatabase graphDb = new EmbeddedGraphDatabase(getStorePath("neo2"), config);
			Transaction tx = graphDb.beginTx();
			Node node1 = graphDb.createNode();
			Node node2 = graphDb.createNode();
			node1.createRelationshipTo(node2, MyRelTypes.TEST);
			node2.createRelationshipTo(node1, MyRelTypes.TEST2);
			node1.createRelationshipTo(node2, MyRelTypes.TEST_TRAVERSAL);
			tx.success();
			tx.finish();
			tx = graphDb.beginTx();
			Set<Relationship> rels = new HashSet<Relationship>();
			RelationshipType[] types = new RelationshipType[] { MyRelTypes.TEST, MyRelTypes.TEST2, MyRelTypes.TEST_TRAVERSAL };
			graphDb.getConfig().getNeoModule().getNodeManager().clearCache();

			foreach (Relationship rel in node1.getRelationships(types))
			{
				assertTrue(rels.Add(rel));
			}
			assertEquals(3, rels.size());
			rels.Clear();
			graphDb.getConfig().getNeoModule().getNodeManager().clearCache();
			foreach (Relationship rel in node1.getRelationships())
			{
				assertTrue(rels.Add(rel));
			}
			assertEquals(3, rels.size());

			rels.Clear();
			graphDb.getConfig().getNeoModule().getNodeManager().clearCache();
			foreach (Relationship rel in node2.getRelationships(types))
			{
				assertTrue(rels.Add(rel));
			}
			assertEquals(3, rels.size());
			rels.Clear();
			graphDb.getConfig().getNeoModule().getNodeManager().clearCache();
			foreach (Relationship rel in node2.getRelationships())
			{
				assertTrue(rels.Add(rel));
			}
			assertEquals(3, rels.size());

			rels.Clear();
			graphDb.getConfig().getNeoModule().getNodeManager().clearCache();
			foreach (Relationship rel in node1.getRelationships(Direction.OUTGOING))
			{
				assertTrue(rels.Add(rel));
			}
			assertEquals(2, rels.size());
			rels.Clear();
			graphDb.getConfig().getNeoModule().getNodeManager().clearCache();
			foreach (Relationship rel in node1.getRelationships(Direction.INCOMING))
			{
				assertTrue(rels.Add(rel));
			}
			assertEquals(1, rels.size());


			rels.Clear();
			graphDb.getConfig().getNeoModule().getNodeManager().clearCache();
			foreach (Relationship rel in node2.getRelationships(Direction.OUTGOING))
			{
				assertTrue(rels.Add(rel));
			}
			assertEquals(1, rels.size());
			rels.Clear();
			graphDb.getConfig().getNeoModule().getNodeManager().clearCache();
			foreach (Relationship rel in node2.getRelationships(Direction.INCOMING))
			{
				assertTrue(rels.Add(rel));
			}
			assertEquals(2, rels.size());

			tx.success();
			tx.finish();
			graphDb.shutdown();
		}
	}
}